[小ネタ] EC2 Windows Serverの壁紙について
小ネタです。
以前の登壇資料でも軽く触れていますが、EC2 Windows Serverではログイン時の壁紙にインスタンス自身の情報が表示されており、この機能はEC2ConfigおよびEC2Launchによって行われています。
(壁紙の右上にインスタンス情報が表示される)
本記事ではこの機能の詳細について説明します。
Windowsで壁紙を更新するといえば...
Windowsにおいて壁紙を動的に更新するツールとしてはBgInfoが一番メジャーです。
BgInfoはAzureでも壁紙を更新する拡張機能(BGInfoExtension)として提供されています。
(AzureでBGInfoExtensionをインストールした場合の表示)
ではAWSでもBgInfoを使っているのかというとそうではありません。
Windows Server 2016以降の場合 (EC2Launch v1)
Windows Server 2016以降ではEC2 Windows Serverの初期設定はEC2Launchによって行われます。
EC2LaunchはPowerShellモジュールで実装されており、モジュールの中のSet-Wallpaper
という関数で壁紙の更新を行っています。
この関数の実装は実際に見ていただくとわかるのですが、インスタンスメタデータからインスタンス情報を取得しWin32 APIのSystemParametersInfo
関数をP/Invokeで実行し壁紙を更新するという極めて愚直な実装となっています。
実装の一部を以下に引用します。
# 最新のモジュール(Ver.1.3.2003040)から処理を一部引用
# Import-WallpaperUtil.ps1
function Import-WallpaperUtil
{
try
{
$check = [WallpaperUtil]
}
catch
{
Add-Type -TypeDefinition @"
using System.Runtime.InteropServices;
using System;
namespace WallpaperUtil {
public static class Helper {
private static uint SPI_SETDESKWALLPAPER = 20;
private static uint SPI_GETDESKWALLPAPER = 115;
private static uint SPIF_UPDATEINIFILE = 0x01;
private static uint SPIF_SENDWININICHANGE = 0x02;
private static uint MAX_PATH = 1024;
public static void SetWallpaper(string src) {
PInvoke.SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, src, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
}
public static String getWallpaper() {
var path = new String('\0', (int)MAX_PATH);
PInvoke.SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, path, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
path = path.Substring(0, path.IndexOf('\0'));
return path;
}
}
public static class PInvoke {
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint SystemParametersInfo(
uint action, uint uParam, string vParam, uint winIni);
}
}
"@
}
# Set-Wallpaper.ps1より引用
function Set-Wallpaper
{
param (
[Parameter(Position=0)]
[switch] $Initial
)
if (Test-NanoServer)
{
return
}
# Import the wallpaper util methods.
Import-WallpaperUtil
# ・・・中略・・・
try
{
Add-Type -AssemblyName System.Windows.Forms
$fontStyle = "Calibri"
$fontSize = 12
Write-Log "Rendering instance information on wallpaper"
$width = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Width
$height = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Height
$textfont = New-object System.Drawing.Font($fontStyle, $fontSize, [System.Drawing.FontStyle]::Regular)
$textBrush = New-Object Drawing.SolidBrush ([System.Drawing.Color]::White)
$proposedSize = New-Object System.Drawing.Size([int]$width, [int]$height)
$messageSize = [System.Windows.Forms.TextRenderer]::MeasureText($message, $textfont, $proposedSize)
if (-not $currentWallpaperPath)
{
# Check and create a new wallpaper if no wallpaper is set in current system.
Write-Log "No wallpaper is set.. Setting wallpaper with custom color"
$bgrRectangle = New-Object Drawing.Rectangle(0, 0, [int]$width, [int]$height)
$bgrBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::Navy)
$bmp = New-object System.Drawing.Bitmap([int]$width, [int]$height)
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
$graphics.FillRectangle($bgrBrush, $bgrRectangle)
}
else
{
# Get the bitmap from the current wallpaper and set the size to be fit in screen.
Write-Log "Wallpaper found.. Rendering instance information on current wallpaper"
$srcBmp = [System.Drawing.Bitmap]::FromFile($originalWallpaperPath)
$bmp = New-Object System.Drawing.Bitmap($srcBmp, $width, $height)
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
$srcBmp.Dispose()
}
# Set the position and size of the text box with rectangle.
$rec = New-Object System.Drawing.RectangleF(($width - $messageSize.Width - 20), 30, ($messageSize.Width + 20), $messageSize.Height)
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
$graphics.DrawString($message, $textfont, $textBrush, $rec)
# Save the new wallpaper in destination defined above.
$bmp.Save($customWallpaperPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
# Finally, set the wallpaper!
[WallpaperUtil.Helper]::SetWallpaper($customWallpaperPath)
Write-Log "Successfully rendered instance information on wallpaper"
}
catch
{
Write-Log ("Failed to render instance information on wallpaper {0}" -f $_.Exception.Message)
}
finally
{
if ($graphics)
{
$graphics.Dispose()
}
if ($bmp)
{
$bmp.Dispose()
}
}
# Before finishing the script, complete the log.
Complete-Log
}
そしてこのSet-Wallpaper
を呼び出すバッチファイルがAdministrator
のスタートアップフォルダに配置されており、ログインの都度呼び出される様になっています。
Windows Server 2012 R2以前の場合 (EC2Config)
Windows Server 2012 R2以前ではEC2 Windows Serverの初期設定はEC2Configによって行われます。
EC2ConfigはWindowsサービスで実装されており、壁紙の更新は同梱されているEc2WallpaperInfo.exe
によって行われています。
処理の実装としてはEC2Launchの場合と同じ模様です。
(逆にEC2LaunchがEC2Configの実装を移植している雰囲気を感じます...)
また、EC2Configの場合はSettings
フォルダにあるWallpaperSettings.xml
で壁紙に表示する内容をある程度コントロールできます。
このEc2WallpaperInfo.exe
を呼び出すショートカットファイルがAllUsers
のスタートアップフォルダに配置されており、ログインの都度呼び出される様になっています。
[2020年7月9日追記] EC2Launch v2の場合
先日EC2ConfigおよびEC2Launch v1の後継バージョンであるEC2Launch v2がリリースされました。
EC2Launch v2はGo言語で実装されたWindowsサービスで、壁紙の描画機能はプログラム本体であるec2launch.exe
により行われています。
また、これまでの実装とは異なりベースとなる壁紙の画像ファイルを設定により指定可能となっており、デフォルトではC:\ProgramData\Amazon\EC2Launch\wallpaper\Ec2Wallpaper.jpg
を使う様になっています。
ちなみに呼び出しタイミングはEC2Configと同様とでした。
最後に
ざっとこんな感じです。
普段何気なく利用している機能ですが、実装を知っておくとトラブルシューティングの際に役に立つこともあると思います。